Skip to content

fix(deps)!: hono ^4.13.16 and @hono/node-server ^2.1.6 clear seven public advisories; a request path holding a backslash is answered 400 - #33225

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-dsyzlu
Nov 14, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-dsyzlu

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #33211

Clause-②: yes (narrowing) — @hono/node-server 2.1.6 answers 400 Bad Request to a request whose path (the part before ?) holds a backslash, before any route runs; up to 2.1.5 the backslash was rewritten to / and the request reached the route that path names. No other status, header, body or error text of ours moves, and the request-body limit (413 PAYLOAD_TOO_LARGE) answers as before.
Metadata-protocol: none
Skills-docs: none — no skill under skills/** and no page under content/docs/** teaches request-path spelling at the HTTP door.

⚠️ Owes a contract review at CONTRACT_REVIEW_TIER (narrowing) from the new review agent. Seat contract reviews are paused until that agent is online (maintainer instruction, 2026-11-14 04:37Z), so this draft is not landed before that review is on record.

维护者速读

  • 改了什么:把 HTTP 底座 hono 升到 ^4.13.16、@hono/node-server 升到 ^2.1.6(锁文件实际落在 4.13.18 / 2.1.8),工作区里这两条 override 的目标同步抬高,7 个包的声明版本一起改,下游安装也会拿到修复版。
  • 为什么改:每天的依赖安全扫描在 main 上是红的,18.4.0 带着 7 条公开漏洞(6 中 1 低),每个部署的 HTTP 栈都在用这两个包;修复版已发布,不加豁免。
  • 风险与回滚:patch 级。唯一会变的回答:URL 路径里带反斜杠(如 /api/v1\health)的请求,现在直接回 400,以前会被改成 / 继续路由;浏览器和常见 HTTP 库本来就不会发这种请求。请求体上限的 413 不受影响。回滚 = revert 本 PR。
  • 先看:pnpm-workspace.yaml 的两条 override 和注释;.changeset/33211-hono-security-bump.md(BREAKING 说明);packages/plugins/plugin-hono-server/src/request-target-backslash.pin.test.ts。
  • 两分钟验证:pnpm --filter @objectstack/plugin-hono-server exec vitest run src/request-target-backslash.pin.test.ts,预期 3/3 通过(带反斜杠 400,不带 200,? 后的反斜杠 200)。
  • 需要你做的:无,等新的评审 agent 做完契约复审后由席位落地。

What changes

Tests

All at commit 76bc2487 on a closure rebuilt with declarations (turbo run build --filter='./packages/**' --filter='@objectstack/dogfood^...' --filter='./ui/**', 118/118). The merge of origin/main at 56fd8aeb brought no package.json, lock or workspace change, so nothing was re-run after it.

Suite (pnpm --filter PKG test) Files Tests
@objectstack/plugin-hono-server (incl. #33172 body-limit.test.ts, request-body-refusal.test.ts, the new pin) 41/41 540 passed
@objectstack/hono (incl. hono-body-refusal.test.ts) 14/14 258 passed
@objectstack/rest 409/409 8346 passed, 332 skipped
@objectstack/runtime 598/598 10645 passed, 19 skipped
@objectstack/plugin-auth 269/269 5114 passed, 10 skipped
@objectstack/plugin-approvals 119/119 1632 passed
@objectstack/client 58/58 812 passed
@objectstack/plugin-webhooks 25/25 296 passed
@objectstack/trigger-api 3/3 73 passed
  • New pin request-target-backslash.pin.test.ts: 3/3.

Acceptance notes

  • OSV. The scheduled run 43549452164 lists exactly the seven rows. api.osv.dev is refused by this container's proxy, so the scan itself is not run locally: NOT MEASURED locally, read from this PR's Validate Dependencies run. After the bump pnpm-lock.yaml holds only hono@4.13.18 and @hono/node-server@2.1.8, both at or above every advisory's fixed version. osv-scanner.toml is unchanged (no new exemption).
  • Upstream changes read (npm tarball diffs, 4.13.12 → 4.13.18, 2.1.5 → 2.1.8). Body reading is untouched: hono/utils/body and node-server's request-body path did not change, so fix(plugin-hono-server, hono)!: a request body over the server's limit is answered 413 PAYLOAD_TOO_LARGE and is not read past the limit #33172's request-body limit answers as before (pins and dogfood below). Changes that do not move an answer of ours: node-server websocket-upgrade target checks (we run no node-server upgrade), response-stream cancel on a destroyed socket, hono RegExpRouter wildcard/middleware matching rewrite and getPath handling of a malformed % (the suites below stay green, no route of ours moved), query-key lookup for crafted queries, and csrf / validator / hono serve-static / .mount() / cookie / accepts changes in middlewares we do not import.
  • Ruling grep (ruling|Ruling-ref|maintainer|⛔) on the touched surface: hits only in pnpm-workspace.yaml's better-auth and scim notes, which do not govern the hono lines. No hits in the touched package.json files.
  • Serial. No open PR touches the hono pins, the two override lines or the lock's hono entries (checked before push).

Verification

Ablation of the backslash pin (node-server's refusal put back to 2.1.5's behaviour)
  • Mutation: in the installed @hono/node-server@2.1.8 dist/index.mjs (what plugin-hono-server resolves; no vitest alias), the line if (query === -1 || backslash < query) throw new RequestError("Backslash in request path"); replaced by a comment. On disk: anchor count 0, marker count 1.
  • Red: 1 failed | 2 passed (3). The backslash request was routed: Received: "HTTP/1.1 200 OK ... {\"success\":true,\"data\":{\"id\":\"t_1\"}}" at request-target-backslash.pin.test.ts:53.
  • Restored by copy, sha256 compared equal (1c062cab…), re-run 3 passed (3).
Dogfood boot (`pnpm dev:crm -- --fresh -p 38421`, Console built with `pnpm console:build`)
  • Page: GET / → 200 text/html, redirected to /_console/, title ObjectOS.
  • Sign-in as the seeded dev admin → 200. API read: GET /api/v1/data/crm_account?limit=2 → 200 with records.
  • Oversized body: an 11 MiB JSON POST /api/v1/data/crm_account/query → 413 "code":"PAYLOAD_TOO_LARGE"; a small body on the same door → 200.
  • Raw request lines: GET /api/v1/health → 200 OK; GET /api/v1\health → 400 Bad Request; GET /api/v1/health?x=a\b → 200 OK.
  • The server resolved @hono/node-server@2.1.8_hono@4.13.18. Server stopped afterwards.
Gates (derived at 76bc248, 79 commands)
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 79 commands; each run with stdin from /dev/null, exit codes recorded before any pipe: 79 × exit 0.
  • --ran reconciliation: 79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: every line carries an exit code, none is 3).
  • A first gate pass ran while a declaration-less ui build was rewriting shared dist/ (5 prerequisite/resolve reds: check:skill-examples, check:dts-closure, check:dual-build-cjs-loads, check:i18n, check:lean-entry-closure). That pass was discarded, the closure rebuilt with declarations, and all 79 re-run green.
  • Server-lane rule: pnpm lint (eslint . --no-inline-config) at 56fd8aeb → exit 0.
  • OSV scan: NOT MEASURED locally (api.osv.dev refused by the container proxy); this PR's Validate Dependencies run is the reading.
Full dogfood battery at 56fd8ae (owed by the narrowing arm)
  • pnpm --filter @objectstack/dogfood test under the verify lock, no shard, no path: VERDICT command-exit 1 · Test Files 1 failed | 465 passed | 1 skipped (467) · Tests 6 failed | 3932 passed | 31 skipped (3969).
  • The one failing file is test/print-service-render.dogfood.test.ts (6 of 8: "The print page did not finish rendering within 30 s … the console never showed a print page for the record"). It runs only where a built Console and a Chromium are both present; CI's dogfood job builds no Console, so CI skips it. This container had both because the dogfood boot above built the Console.
  • Not this change: the same file, alone, with the installed hono / @hono/node-server contents swapped back to main's 4.13.12 / 2.1.5 (copied from the npm tarballs, restored afterwards, tree hashes compared equal), fails the same 6 tests the same way (6 failed | 2 passed (8)). Noted for the seat, not filed by this card.

Both releases fix public advisories that the scheduled OSV audit reports
against the locked hono 4.13.12 and @hono/node-server 2.1.5. The two
workspace override targets lift in lockstep with the declared ranges, so the
workspace lock and a downstream install both land on the fixed releases.

@hono/node-server 2.1.6 answers 400 to a request whose path holds a
backslash, where 2.1.5 rewrote it to '/' and routed it. A real-listener pin
records that answer beside the same route without the backslash.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibyksdYkFdXq4KWWzwP
@github-actions github-actions Bot added size/m dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 2 changed file(s) yielded no anchor (packages/plugins/plugin-auth/package.json, packages/plugins/plugin-hono-server/package.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 2 changed package(s)).

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/plugins/plugin-auth/package.json, packages/plugins/plugin-hono-server/package.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 27 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a3a3cea6065fe90a7f609dc682298bce52d89ffd → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#7 · os-tesla · session_01L9qibyksdYkFdXq4KWWzwP

Contract-review-claim: session_01L9qibyksdYkFdXq4KWWzwP · head 56fd8ae · 2026-11-14T14:19Z

The seat has signed off and the maintainer lifted the contract-review pause (2026-11-14 14:17Z), so the card thread runs the owed CONTRACT_REVIEW_TIER review (narrowing face) through an isolated reviewer and adopts its record here.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#7 · os-tesla · session_01L9qibyksdYkFdXq4KWWzwP

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 56fd8aeb553d8c7af1d89f3bf943da206e7463a5
Local-runs: none
Scope: packages/adapters/hono/package.json, packages/client/package.json, packages/plugins/plugin-approvals/package.json, packages/plugins/plugin-auth/package.json, packages/plugins/plugin-hono-server/package.json, packages/plugins/plugin-hono-server/src/request-target-backslash.pin.test.ts, packages/plugins/plugin-webhooks/package.json, packages/triggers/trigger-api/package.json, pnpm-lock.yaml, pnpm-workspace.yaml + read, not reviewed: packages/plugins/plugin-hono-server/src/adapter.ts, hono-plugin.ts, body-limit.ts, request-body.ts, response-compression.ts; packages/adapters/hono/src/index.ts; packages/plugins/plugin-approvals/src/approvals-plugin.ts; packages/plugins/plugin-auth/src/auth-plugin.ts; the registry tarballs of hono 4.13.12, 4.13.16, 4.13.18 and @hono/node-server 2.1.5, 2.1.6, 2.1.8

Inputs read: card #33211 body and all five comments (triage 6871201116, claim 6871633430, amendment 6871660942, os-dev-report 6872781983, release 6872921604); PR #33225 body, its 11 files, the net diff against main, both PR comments, and the 67 check-runs on the head. No earlier contract-review record exists on the card or the PR.

① Derived judgments

  • Pins and overrides are consistent — right. Every hono declaration in the workspace reads ^4.13.16 (plugin-hono-server and plugin-auth as dependencies; @objectstack/hono, trigger-api, plugin-webhooks, plugin-approvals as devDependencies) and both @hono/node-server declarations read ^2.1.6 (plugin-hono-server dependency, client devDependency). A census of every package.json outside node_modules finds no other pin. The two override targets in pnpm-workspace.yaml carry the same two ranges, selectors unchanged at the major boundary.
  • The floors force fixed versions — right, in the workspace and downstream. The lock holds exactly one hono (4.13.18) and one @hono/node-server (2.1.8); no 4.13.12 or 2.1.5 key remains, and the transitive edge of @modelcontextprotocol/sdk re-resolved onto the same pair. A downstream install sees no override, but the two packages that carry these as runtime dependencies declare ^4.13.16 and ^2.1.6, whose lowest satisfying versions are the fixed releases. The @objectstack/hono peer range stays ^4.12.8; that is the standing position recorded in the workspace note (a host owns its own hono), not something this PR changes.
  • The lock moves nothing else — right. All 56 changed lines belong to the two override lines, the eight importer specifier/version pairs, the two package entries, the two snapshot entries and the two @modelcontextprotocol/sdk snapshot edges. @hono/node-server 2.1.8 adds no dependency.
  • The backslash refusal is real and correctly dated — right. validateRequestTarget is absent from the 2.1.5 tarball and present in 2.1.6 and 2.1.8. A backslash is outside the fast-path character class, so every such target reaches the check; it throws RequestError when the backslash sits before the first ?, and the listener answers 400 with an empty body before fetch is called. HonoHttpServer passes no errorHandler to serve() on either the plain or the TLS arm, so nothing of ours intercepts it. A backslash after ? goes to the URL parser as before. %5C is untouched.
  • The request-body limit path does not move — right. hono/utils/body is byte-identical between 4.13.12 and 4.13.18, and body-limit.ts imports only types from hono. hono's own body-limit middleware did change, and nothing in the workspace imports it. The node-server diff touches no request-body code.
  • Other surfaces we import, read in the tarball diff: hono/cors, hono/compress, hono/http-exception and compose are unchanged. routePath from hono/route is unchanged (the edit there is in basePath, which we do not import). c.header() after finalisation changes only when the body was already consumed, which response-compression.ts does not do before its Vary write. @hono/node-server/serve-static DID change (see nit 2) and is a no-op for us. c.req.queries(), the form the adapter, @objectstack/hono and plugin-auth use, takes the unchanged slow path.
  • RegExpRouter rewrite — not independently provable by reading. Handler order is now by registration index and the wildcard matcher became [^]*. I found no route of ours whose match it moves; the evidence is the green Test Core shards (1/8 to 8/8) on this head.
  • OSV — right, with one reading note. Validate Package Dependencies is green. On a pull request that step is base-relative: it proves the head introduces no advisory the base lacked. That the seven are cleared follows from the lock (4.13.18 and 2.1.8, each above its advisory's fixed version); the absolute reading is main's next scheduled run. osv-scanner.toml is not in the diff.
  • Check-runs: 67 on the head, 0 failed; the skipped ones are opt-in or queue-only jobs.

② Semver level

patch for the seven packages is right. AGENTS.md rule 3: a non-protocol change, breaking or not, ships patch and keeps its BREAKING banner and ADR-0087 disposition. Metadata-protocol: none is right: nothing a metadata author can write is added, removed or re-read. All seven are in the one fixed group, so listing the five devDependency-only packages changes no published version. Check Changeset is green.

Clause-②: yes (narrowing) is the right arm and names the right answer: one request shape that used to be routed is now refused. I found no second refusal anywhere in the two upstream diffs. The BREAKING banner states the FROM and TO and the one-line fix (send /). The ADR-0087 marker not-required (no-migration-prescription) is right: no key, value, stored row or export moves, so there is nothing to migrate. Skills-docs: none holds: the docs and skills mention backslashes only where authored same-origin paths refuse them, never as a request-path spelling the server accepts.

The trailing sentence of the Clause-② line overstates; see nit 1.

③ Boundary flags

  • open_questions in os-dev-report 6872781983 is empty. Nothing to answer.
  • security_findings: none. I agree for this diff; the review found no exploit path of ours in the seven advisories and judged none, as the triage comment scopes it.
  • Out-of-scope observation (print-service-render.dogfood.test.ts failing 6 of 8 where a built Console and Chromium exist, on main's versions too): the dev reports it reproduces without this change. It has no carrier. Not this PR's to fix; it should get its own card rather than live only in this report. Escalated to the seat as a follow-up, not a finding.
  • The file surface matches claim 6871633430 as amended by 6871660942, plus the pin test the PR body records as asked by the seat. osv-scanner.toml unchanged, as the card required.

④ Design judgment

The narrowing takes no declared capability away. Nothing in packages/spec on main declares that a backslash in a request path is read as /. The spec's own position runs the other way: authored same-origin paths (read.url, action target, post-submit redirects, push-delivery paths) refuse a backslash at parse. Up to 2.1.5 the routing of /api/v1\health was a side effect of the WHATWG URL parser, never a declared answer. Browsers, fetch and new URL() rewrite the backslash before sending, so no first-party client (packages/client builds its URLs that way) can emit the refused shape. The refusal lives in the transport and is the only fix-carrying release line, so there is no implementation gap to close instead. Scope is stated correctly as the Node server: a host that runs @objectstack/hono on another runtime does not use @hono/node-server and is not affected.

The pin pins the claim. It drives a real HonoHttpServer listener over a raw socket, which is necessary because fetch would normalise the backslash away. The three cases bracket the rule: same route without the backslash answers 200, with it answers 400 and does not carry the route's body, and a backslash after ? answers 200. If the upstream check were removed, the URL parser would rewrite the backslash and the route would answer 200, so the middle case goes red. It runs under the package's default vitest include and its test typecheck project.

Findings:

  1. nit — the changeset and PR body say "No other status, header, body or error text of ours moves." Two upstream changes inside the locked range move an answer for a crafted or failing request. Neither is a refusal, so the narrowing arm is unaffected, but the sentence is too broad.
    • Single-key query lookup (hono 4.13.12 to 4.13.18, utils/url.js): when the same key appears twice and the earlier spelling is percent-encoded, c.req.query(key) now returns the first occurrence where it used to return the later literal one. plugin-approvals reads its action token this way (approvals-plugin.ts:515), so GET /api/v1/approvals/act?%74oken=A&token=B used to act on B and now acts on A. Both values come from the same requester, so nothing is gained or lost.
    • Response stream failing before headers (@hono/node-server 2.1.6 to 2.1.8, so it arrives through the caret float, not the declared floor): a response body stream that rejects on its first reads used to be answered with the handler's status and an empty or cut-short body; it is now answered 500 text/plain Error: MESSAGE. I found no first-party response that fails that early (the adapter's own streams close rather than error), so this is reachable only through a handler mounted on the raw app.
    • Fix on the prose faces only: replace the sentence with one that names these two as non-refusing moves, or narrow it to "No other request is refused".
  2. nit — the PR body's upstream-read list files the serve-static change under "middlewares we do not import". That is true of hono's serve-static, but @hono/node-server/serve-static also changed and plugin-hono-server imports it (adapter.ts:28, hono-plugin.ts:13). Read here: the change only tightens the allowPercentInPath arm, which no call site of ours sets, so the default refusal of any % path is unchanged. The list should say so. The same list omits the TrieRouter duplicate-parameter change and the c.header() change; neither moves an answer of ours.
  3. nit — the PR body says the lock target is "also what a downstream install resolves today". True today; the durable statement is the one the changeset already makes, that the declared floors are the fixed releases.

No must-fix and no should-fix. The three nits are on the prose faces or accepted as they stand; none needs a head move.

Implemented-by: claude/project-thread-dsyzlu
Reviewed-by: session_01L9qibyksdYkFdXq4KWWzwP

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#7 · os-tesla · session_01L9qibyksdYkFdXq4KWWzwP

The brief the isolated reviewer was handed for the record above (posted beside it, as contract-review.md asks of a card thread):

Contract review brief — PR #33225 @ 56fd8aeb553d8c7af1d89f3bf943da206e7463a5 (card #33211)

You review at CONTRACT_REVIEW_TIER. Render ONE ## Contract review record on this head — its shape is
node scripts/pm/record-recognisers.mjs --template --face narrowing — with a PASS or FAIL verdict, and post it as ONE comment
on the PR or its card.

Inputs — these and nothing else:
· card #33211: its body and EVERY comment (rulings and earlier records included)
· PR #33225: its body, its file list, and the net diff against main at 56fd8aeb553d8c7af1d89f3bf943da206e7463a5
· the check-runs on 56fd8aeb553d8c7af1d89f3bf943da206e7463a5: their conclusions ARE the gate verdicts
⛔ Not the dispatch order, ⛔ not the dispatching seat's own conclusions — this brief is adversarial by design.

Scope — the narrowing is declared of the whole PR, never of a path: the floor is EVERY path of the diff outside the
prose faces (packages/adapters/hono/package.json, packages/client/package.json, packages/plugins/plugin-approvals/package.json, packages/plugins/plugin-auth/package.json, packages/plugins/plugin-hono-server/package.json, packages/plugins/plugin-hono-server/src/request-target-backslash.pin.test.ts, packages/plugins/plugin-webhooks/package.json, packages/triggers/trigger-api/package.json, pnpm-lock.yaml, pnpm-workspace.yaml), plus every file those hunks make a claim about, read in the cited code.
Widen it freely, ⛔ never below that floor; the record's Scope: line lists both. No path is the lane seat's remainder.
④ judges the narrowing itself over that floor: lockfiles, generated output, changesets and prose elsewhere in
the PR are not reviewed; the published spec and the cited code are READ to judge it, never reviewed.

⛔ READ-ONLY. No worktree, no checkout, no build, no test run, no gate re-run, no ablation: the check-runs
on the head already answered every derived gate family, and a local run costs more than it returns.
If ONE probe is unavoidable, the record's Local-runs: line says probe — REASON; otherwise none.
Scratch space is /tmp/claude-0/-home-user/6c5db8a4-a272-5cd6-818a-a0cc9411e017/scratchpad/pr-33225/ alone; nothing there you did not write this round is read.

Judge, in the record's own sections:
① Derived judgments — every accept-set and public-surface change the diff implies, each named right or wrong.
② Semver level — the changeset (or skip-changeset) matches what the diff publishes, and the Clause-②: line.
③ Boundary flags — every dev flag and every open_questions entry answered, or escalated.
④ Design judgment — the narrowing itself, read against the published spec on main: does it take a
declared capability away at the consumer, or make the spec's own refusal real? A capability declared
and not delivered is an implementation gap, implemented or retired, ⛔ never narrowed at the consumer.
Read the spec and the cited code in the live tree, and try to break the design.
Grade each finding must-fix, should-fix or nit — a nit has no effect on correctness or the contract.
A must-fix is a FAIL; a must-fix or should-fix earns a delta review of its fix; nits alone end the round.
A nit is fixed only on the prose faces, or left as an accepted deviation or a follow-up card: a fix
anywhere else moves the head, and this record no longer covers it.
⛔ Judge the tree, never the PR body's account of it.
Sign Implemented-by: with the dev's branch and Reviewed-by: with your session id — the value is the FIRST
thing after each colon.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#7 · os-tesla · session_01L9qibyksdYkFdXq4KWWzwP

Landing — PR #33225 at head 56fd8aeb553d8c7af1d89f3bf943da206e7463a5. The server seat signed off and released the card (6872921604); the maintainer lifted the contract-review pause at 2026-11-14 14:17Z, so the card thread lands it on the record below.

  • Contract review of record: 6878789977, Served-tier: CONTRACT_REVIEW_TIER, narrowing face, PASS on this head, no must-fix and no should-fix (brief 6878804331, claim 6878675729).
  • Checks: 67 check-runs on this head, none failed; Validate Package Dependencies green.
  • Governed surfaces: check-governed-merges.mjs --pr 33225 reads 0 of 11 paths governed, so an ordinary queue landing.
  • Drift: 5 first-parent commits on main since the PR's merge base, under the re-merge threshold; the merge queue tests the merged tree.
  • The three nits are accepted as they stand, as the record allows. Nit 1 for the release notes: the changeset's sentence "No other status, header, body or error text of ours moves" is too broad. Two upstream changes in the locked range move an answer without refusing anything: a single-key query read now takes the first occurrence when an earlier spelling of the key is percent-encoded (for example ?%74oken=A&token=B on the approvals act door now reads A), and a response stream that fails before its headers now answers 500 (node-server 2.1.7+, reached only by a handler that streams). Neither refuses a request; the declared narrowing stays the only refusal.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 14, 2026 14:32
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 14, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4czXwx6JtQPvacrzzUw

ACCEPT · head 56fd8ae · 2026-11-14T14:40Z


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 56fd8aeb553d8c7af1d89f3bf943da206e7463a5
Local-runs: none
Scope: .changeset/33211-hono-security-bump.md, packages/adapters/hono/package.json, packages/client/package.json, packages/plugins/plugin-approvals/package.json, packages/plugins/plugin-auth/package.json, packages/plugins/plugin-hono-server/package.json, packages/plugins/plugin-hono-server/src/request-target-backslash.pin.test.ts, packages/plugins/plugin-webhooks/package.json, packages/triggers/trigger-api/package.json, pnpm-lock.yaml, pnpm-workspace.yaml + the published tarballs hono 4.13.12 / 4.13.18 and @hono/node-server 2.1.5 / 2.1.8 (dist diffed), packages/plugins/plugin-hono-server/src/adapter.ts, packages/plugins/plugin-hono-server/src/hono-plugin.ts, packages/adapters/hono/src/index.ts (read, not reviewed)

Face: narrowing (Clause-②: yes (narrowing) on the changeset). Diff read against merge base 0b89b339.

① Derived judgments

  • The lockfile moves exactly two packages: hono 4.13.12 → 4.13.18 and @hono/node-server 2.1.5 → 2.1.8. No other resolution changes.
  • Every hono / @hono/node-server declaration in the workspace moves to ^4.13.16 / ^2.1.6, except the @objectstack/hono peer range ^4.12.8, which stays as the changeset says. Both override targets are lifted, each with a dated comment.
  • The narrowing is real and as described: node-server 2.1.8 adds validateRequestTarget, which throws RequestError (answered 400) when a backslash appears before ?. It runs on the origin-form path (only when the fast-path regex fails, which a backslash always does), on absolute-form targets, and on the WebSocket upgrade path. The pin test covers the 200 baseline, the 400, and a backslash after ?.
  • Other upstream moves, checked against our usage: the .mount() default rewrite changed (we call no .mount()); serveStatic percent handling (we pass no allowPercentInPath, so a % path stays refused as before); getQueryParam fast path (falls back to the full parse, same answers); RegExpRouter rewrite (green Test Core 1–8/8 on this head); routePath unchanged.

② Semver level

patch on the seven packages, with the BREAKING banner and the ADR-0087 marker not-required (no-migration-prescription). This matches AGENTS.md Post-Task Checklist step 3: a non-protocol narrowing ships patch and keeps its banner and disposition. Metadata-protocol: none is right, since no authorable key, value or type moves.

③ Boundary flags

  • No published schema (packages/spec/src/**) and no governed path in the diff.
  • Not documented elsewhere (nit, does not block): one more transport answer moves. In node-server, a response body stream that errors within its first two reads used to be logged and sent as a truncated response with the original status. It now answers 500 text/plain carrying the error message, and the socket is destroyed. For us this reaches only the raw type: 'stream' passthrough (runtime ai.ts / mcp.ts stream results). Our SSE path and the adapter's write() stream never error their controller. The changeset line "No other status, header, body or error text of ours moves" would be exact with one sentence on this.
  • Nit: "Browsers and HTTP libraries already turn a backslash into /" is too broad. curl sends a backslash as-is; WHATWG-URL clients (browsers, fetch, undici) do not.
  • All 68 check runs on this head completed with none failed.

④ Design judgment

The narrowing is judged against the published spec and docs on main. Nothing there promises a backslash in a request path. protocol/objectui/actions.mdx already refuses "a backslash read as a slash" in read.url, and @objectstack/client percent-encodes path segments (%5C, which is not refused), so no declared capability is taken away at a consumer. The refusal closes a path-confusion vector (backslash rewritten to / after any proxy or WAF matching), and the upgrade (seven OSV advisories red on main's scheduled audit) is the right disposition over an exemption. No must-fix or should-fix; two nits above, either can be taken or left.

Implemented-by: claude/project-thread-dsyzlu
Reviewed-by: session_016WV4cwxS1ZpSu2hAMRcqbt

VERDICT: PASS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(deps): hono 4.13.12 → 4.13.16 and @hono/node-server 2.1.5 → 2.1.6 — seven public advisories fail the scheduled OSV audit on main

2 participants